"""
Tower of Typing - Main Game Module
A typing-based tower defense game with animated sprites and multiple screens

This module contains the main game logic, UI management, and level progression system.
Follows proper OOP design patterns with pygame.sprite for collision detection.
"""

import pygame
import random
import json
import os
from enemy_config import (BASE_WIDTH, BASE_HEIGHT, ENEMY_BASE_SPEED_MIN, ENEMY_BASE_SPEED_MAX, 
                         ENEMY_LEVEL_SPEED_SCALE, LEVEL_ENEMY_COUNTS, 
                         PLAYER_LIVES, ENEMY_WORDS_PT, get_text)
from game_objects import (
    Explosion,
    Defender,
    Enemy,
    load_textbox_images,
    load_player_sprites,
    load_explosion_frames,
    create_tower_rect,
)
from screens import (MenuScreen, InstructionsScreen, GameScreen, PauseScreen, 
                    LoseScreen, HighscoresScreen, CongratulationsScreen)

# Initialize Pygame
pygame.init()
pygame.font.init()
pygame.mixer.init()

# Use pygame.SCALED for automatic scaling with logical resolution
LOGICAL_W, LOGICAL_H = BASE_WIDTH, BASE_HEIGHT
flags = pygame.FULLSCREEN | pygame.SCALED
WIN = pygame.display.set_mode((LOGICAL_W, LOGICAL_H), flags)
WIDTH, HEIGHT = LOGICAL_W, LOGICAL_H  # Use logical dimensions throughout the game
pygame.display.set_caption("Tower of Typing")

# Background
ASSETS_DIR = os.path.join(os.path.dirname(__file__), "Assets")
BACKGROUNDS_DIR = os.path.join(os.path.dirname(__file__), "Backgrounds")
BACKGROUND_IMG_PATH = os.path.join(ASSETS_DIR, "Background.png")
background_img = None


def load_background_for_level(level):
    """
    Load and scale the appropriate background based on level
    
    Args:
        level (int): Current game level (1-10)
    """
    global background_img
    
    # Determine which background file to use based on level
    if 1 <= level <= 4:
        bg_file = "BackgroundLvl1-4.png"
    elif 5 <= level <= 6:
        bg_file = "BackgroundLvl5-6.png"
    elif 7 <= level <= 8:
        bg_file = "BackgroundLvl7-8.png"
    elif 9 <= level <= 10:
        bg_file = "BackgroundLvl9-10.png"
        
    bg_path = os.path.join(BACKGROUNDS_DIR, bg_file)
    _bg = pygame.image.load(bg_path).convert()
    # Scale to logical dimensions - pygame.SCALED will handle final scaling
    background_img = pygame.transform.smoothscale(_bg, (LOGICAL_W, LOGICAL_H))


# Load initial background for level 1
load_background_for_level(1)

# TextBoxes
textbox_images = load_textbox_images()

# Colors
WHITE = (255, 255, 255)
BLACK = (10, 10, 10)
RED = (220, 20, 60)
ORANGE = (255, 140, 0)
GREEN = (34, 139, 34)
BLUE = (50, 130, 255)
PURPLE = (148, 0, 211)
GRAY = (180, 180, 180)

# Font sizes optimized for logical resolution (900x520)
FONT_SIZE = 50  # Base font size for logical resolution
FONT = pygame.font.SysFont("consolas", FONT_SIZE)
MED = pygame.font.SysFont("consolas", 64)
MED_OUTLINE = pygame.font.SysFont("consolas", 66)
BIG = pygame.font.SysFont("consolas", 120)
BIG_OUTLINE = pygame.font.SysFont("consolas", 124)

# Load player sprites
player_idle, player_shooting = load_player_sprites()

# Load explosion sprites
explosion_frames = load_explosion_frames()

# Sound Effects
SOUNDS_DIR = os.path.join(ASSETS_DIR, "Sounds")

# Load background music
background_music_path = os.path.join(SOUNDS_DIR, "Background.mp3")
pygame.mixer.music.load(background_music_path)
pygame.mixer.music.set_volume(0.5)
# Start background music immediately
pygame.mixer.music.play(-1) # loop indefinitely

# Load sound effects
explosion_sound = pygame.mixer.Sound(os.path.join(SOUNDS_DIR, "Explosion.mp3"))
explosion_sound.set_volume(0.3)
level_up_sound = pygame.mixer.Sound(os.path.join(SOUNDS_DIR, "LevelUp.mp3"))
ouch_sound = pygame.mixer.Sound(os.path.join(SOUNDS_DIR, "Ouch.mp3"))

# Defender/Tower Setup - using logical coordinates
tower_rect = create_tower_rect(HEIGHT)

# Highscore Management
DATA_FILE = "highscore.json"

def load_highscores():
    """
    Load high scores from JSON file
    
    Returns:
        list: List of highscore dictionaries with 'name' and 'score' keys
    """
    try:
        with open(DATA_FILE, "r", encoding="utf-8") as f:
            scores = json.load(f)
            if isinstance(scores, list):
                return scores
            elif isinstance(scores, dict):
                # legacy format, convert to list
                return [scores]
    except (FileNotFoundError, json.JSONDecodeError):
        return []

def save_highscore(name, score):
    """
    Save a new high score to the JSON file
    
    Args:
        name (str): Player name
        score (int): Player score
    """
    scores = load_highscores()
    scores.append({"name": name, "score": score})
    # Sort descending by score, keep top 10
    scores = sorted(scores, key=lambda x: x["score"], reverse=True)[:10]
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(scores, f, ensure_ascii=False, indent=2)

def get_top_highscores():
    """
    Get top 10 high scores
    
    Returns:
        list: Top 10 highscore entries
    """
    return sorted(load_highscores(), key=lambda x: x["score"], reverse=True)[:10]

class LevelManager:
    """
    Manages game level progression, enemy spawning, and transitions
    """
    
    LOWER = [chr(i) for i in range(97, 123)]  # a-z
    UPPER = [chr(i).upper() for i in range(97, 123)]  # A-Z
    WORDS = ["attack", "robot", "circuit", "keyboard", "dog", "cat", "tower", "defend",
             "dragon", "castle", "enemy", "shield", "machine", "gear", "hostile",
             "network", "system", "protocol", "metal"]

    def __init__(self, game):
        """
        Initialize level manager
        
        Args:
            game: Reference to main Game object
        """
        self.game = game
        self.enemies = pygame.sprite.Group()  # Use sprite group for collision detection
        self.level = 1
        self.spawned_count = 0
        self.transitioning = True
        self.transition_timer = 180
        self.transition_alpha = 0
        self.awaiting_boss = False
        self.spawn_delay = 60
        self.spawn_timer = 0
        self.level_complete = False
        self.level_complete_timer = 0

    def start_level_transition(self):
        """Start transition animation for new level"""
        self.transitioning = True
        self.transition_timer = 180
        self.transition_alpha = 0
        self.enemies.empty()  # Clear all enemies
        self.spawned_count = 0
        self.awaiting_boss = False
        self.spawn_timer = 0
        self.level_complete = False
        self.level_complete_timer = 0
        
        # Load the appropriate background for the new level
        load_background_for_level(self.level)

    def spawn_enemy(self):
        """Spawn a regular enemy based on current level"""
        pools = []
        if self.level >= 1:
            pools += self.LOWER
        if self.level >= 3:
            pools += self.UPPER
        if self.level >= 5:
            pools += self.WORDS
        
        text = random.choice(pools)
        
        # Translate word if needed
        if self.game.language == "pt" and text in ENEMY_WORDS_PT:
            text = ENEMY_WORDS_PT[text]
        
        # Color by type
        if text in self.LOWER:
            color = RED
        elif text in self.UPPER:
            color = ORANGE
        elif (self.game.language == "pt" and text in ENEMY_WORDS_PT.values()) or text in self.WORDS:
            color = GREEN
        else:
            color = BLUE
        
        base_speed = random.uniform(ENEMY_BASE_SPEED_MIN, ENEMY_BASE_SPEED_MAX) * (1 + (self.level - 1) * ENEMY_LEVEL_SPEED_SCALE)
        
        enemy = Enemy(text, base_speed, color=color, textbox_images=textbox_images,
                     level_manager=self, game_language=self.game.language)
        self.enemies.add(enemy)
        self.spawned_count += 1

    def spawn_major_bosses(self):
        """Spawn boss enemies for levels 9 and 10"""
        if self.level == 9:
            boss_speed = 1  # Use logical coordinate speed
            boss_text = get_text("boss_texts", self.game.language)[0]
            boss = Enemy(boss_text, boss_speed, color=PURPLE, boss=True, boss_type=3,
                        textbox_images=textbox_images, level_manager=self, 
                        game_language=self.game.language)
            self.enemies.add(boss)
        elif self.level == 10:
            boss_speed = 1  # Use logical coordinate speed
            boss_text_1 = get_text("boss_texts", self.game.language)[1]
            boss_text_2 = get_text("boss_texts", self.game.language)[2]
            
            boss1 = Enemy(boss_text_1, boss_speed, color=PURPLE, boss=True, boss_type=1,
                         textbox_images=textbox_images, level_manager=self, 
                         game_language=self.game.language)
            boss2 = Enemy(boss_text_2, boss_speed, color=PURPLE, boss=True, boss_type=2,
                         textbox_images=textbox_images, level_manager=self, 
                         game_language=self.game.language)
            
            # Position the second boss further to the right so they don't overlap
            boss_offset = 800  # Use logical coordinate offset
            boss2.rect.x += boss_offset
            if hasattr(boss2, 'collision_rect'):
                boss2.collision_rect.x += boss_offset
            
            self.enemies.add([boss1, boss2])
        
        self.awaiting_boss = True

    def update(self):
        """Update level transition and enemy spawning logic"""
        if self.transitioning:
            self.transition_timer -= 1
            total = 180
            if self.transition_timer > total * 0.66:
                alpha = int(255 * (1 - (self.transition_timer - total * 0.66) / (total * 0.34)))
            elif self.transition_timer < total * 0.33:
                alpha = int(255 * (1 - (total * 0.33 - self.transition_timer) / (total * 0.33)))
            else:
                alpha = 255
            self.transition_alpha = alpha
            if self.transition_timer <= 0:
                self.transitioning = False
        else:
            if not self.awaiting_boss:
                if self.spawned_count < LEVEL_ENEMY_COUNTS[self.level]:
                    self.spawn_timer += 1
                    if self.spawn_timer >= self.spawn_delay:
                        self.spawn_enemy()
                        self.spawn_timer = 0


class UIManager:
    """
    Static class for UI rendering utilities
    """
    
    @staticmethod
    def draw_highscore_list(surf, scores, title, font, color, y_start):
        """
        Draw formatted high score list
        
        Args:
            surf (pygame.Surface): Surface to draw on
            scores (list): List of score dictionaries  
            title (str): Title text for the list
            font (pygame.font.Font): Font for title
            color (tuple): RGB color for text
            y_start (int): Starting Y position
        """
        UIManager.draw_text_center(surf, title, BIG, color, y_start - 20)
        small_font = pygame.font.SysFont("consolas", 40)
        y = y_start + 100
        vertical_spacing = 70
        for idx, entry in enumerate(scores):
            name = entry.get("name", "Anon")
            score = entry.get("score", 0)
            txt = f"{idx+1:2d}. {name:12s} - {score}"
            t_surf = small_font.render(txt, True, WHITE)
            surf.blit(t_surf, ((WIDTH-t_surf.get_width())//2, y))
            y += vertical_spacing
    
    @staticmethod
    def draw_text_center(surf, text, font, color, y):
        """
        Draw centered text at specified Y position
        
        Args:
            surf (pygame.Surface): Surface to draw on
            text (str): Text to render
            font (pygame.font.Font): Font to use
            color (tuple): RGB color
            y (int): Y position for text
        """
        txt = font.render(text, True, color)
        surf.blit(txt, ((WIDTH - txt.get_width()) // 2, y))

    @staticmethod
    def draw_fade_box(surf, text, alpha):
        """
        Draw semi-transparent text box with fading effect
        
        Args:
            surf (pygame.Surface): Surface to draw on
            text (str): Text to display in box
            alpha (int): Alpha transparency value (0-255)
        """
        box = pygame.Surface((WIDTH - 200, 100), pygame.SRCALPHA)
        box.fill((255, 255, 255, 200))
        surf.blit(box, (100, HEIGHT // 2 - 60))
        
        words = text.split(" ")
        lines = []
        line = ""
        for w in words:
            if FONT.size(line + " " + w)[0] > WIDTH - 260:
                lines.append(line.strip())
                line = w + " "
            else:
                line += w + " "
        if line:
            lines.append(line.strip())
        
        for i, ln in enumerate(lines):
            s = FONT.render(ln, True, BLACK)
            s.set_alpha(alpha)
            surf.blit(s, (120, HEIGHT // 2 - 40 + i * 26))


class Game:
    """
    Main game class managing all game states, logic, and rendering
    Implements proper screen management and sprite-based collision detection
    """
    
    def __init__(self):
        """Initialize the main game object"""
        self.clock = pygame.time.Clock()
        self.state = "menu"
        self.level_manager = LevelManager(self)
        self.lives = PLAYER_LIVES
        self.score = 0
        
        # Load high scores
        scores = get_top_highscores()
        self.highscore = scores[0] if scores else {"name": "None", "score": 0}
        
        # Input state
        self.entering_name = ""
        self.is_new_high = False
        self.language = "en"  # "en" for English, "pt" for Portuguese
        
        # Create sprite groups for collision detection
        self.explosions = pygame.sprite.Group()
        
        # Store explosion frames for creating explosions
        self.explosion_frames = explosion_frames
        
        # Create defender with proper sprite inheritance
        self.defender = Defender(tower_rect, player_idle, player_shooting)
        
        # Initialize screen objects
        self.screens = {
            "menu": MenuScreen(self),
            "instructions": InstructionsScreen(self),
            "game": GameScreen(self),
            "pause": PauseScreen(self),
            "lose": LoseScreen(self),
            "highscores": HighscoresScreen(self),
            "congratulations": CongratulationsScreen(self)
        }
        
        # Expose commonly used functions for screens
        self.load_highscores = load_highscores
        self.save_highscore = save_highscore
        self.get_top_highscores = get_top_highscores
        self.enemy_counts = LEVEL_ENEMY_COUNTS
        self.tower_rect = tower_rect
        self.explosion_sound = explosion_sound
        self.level_up_sound = level_up_sound
        self.ouch_sound = ouch_sound

    def pause_background_music(self):
        """Pause the background music"""
        pygame.mixer.music.pause()
    
    def resume_background_music(self):
        """Resume the background music"""
        pygame.mixer.music.unpause()
    
    def stop_background_music(self):
        """Stop the background music"""
        pygame.mixer.music.stop()
    
    def ensure_background_music(self):
        """Ensure background music is playing, restart if needed"""
        if not pygame.mixer.music.get_busy():
            pygame.mixer.music.play(-1)  # loop indefinitely
    
    def _qualifies_for_highscore(self):
        """Determine if the current score qualifies for top 10."""
        highscores_sorted = get_top_highscores()
        if len(highscores_sorted) < 10:
            return True
        return self.score >= highscores_sorted[-1]["score"]

    def _transition_state(self, new_state):
        """Handle transitions between screens with shared setup logic"""
        if not new_state:
            return
        if new_state == "restart_game":
            self.start_game()
            return

        self.state = new_state

        if self.state in {"lose", "congratulations"}:
            self.is_new_high = self._qualifies_for_highscore()
            self.entering_name = ""

    def reset_game(self):
        """Reset game state to initial values"""
        self.lives = PLAYER_LIVES
        self.score = 0
        self.level_manager.level = 1
        self.level_manager.enemies.empty()
        self.explosions.empty()
        self.entering_name = ""
        self.is_new_high = False
        
        # Reset level manager state
        self.level_manager.spawned_count = 0
        self.level_manager.transitioning = False
        self.level_manager.transition_timer = 180
        self.level_manager.transition_alpha = 0
        self.level_manager.awaiting_boss = False
        self.level_manager.spawn_delay = 60
        self.level_manager.spawn_timer = 0
        self.level_manager.level_complete = False
        self.level_manager.level_complete_timer = 0
        
        # Load background for level 1
        load_background_for_level(1)

    def start_game(self):
        """Initialize a new game session"""
        self.reset_game()
        self.ensure_background_music()  # Ensure music keeps playing
        self.level_manager.start_level_transition()
        self.state = "instructions"

    def run(self):
        """
        Main game loop handling events, updates, and rendering
        Uses proper screen-based architecture for better organization
        """
        while True:
            dt = self.clock.tick(60)
            
            # Draw background

            WIN.blit(background_img, (0, 0))
            
            # Handle events through current screen
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    return
                
                # Let current screen handle the event
                current_screen = self.screens.get(self.state)
                if current_screen:
                    new_state = current_screen.handle_event(event)
                    if new_state:
                        self._transition_state(new_state)
                        current_screen = self.screens.get(self.state)
            
            # Update current screen
            current_screen = self.screens.get(self.state)
            if current_screen:
                new_state = current_screen.update(dt)
                if new_state:
                    self._transition_state(new_state)
                    current_screen = self.screens.get(self.state)
            
            # Update sprite groups
            self.explosions.update()
            
            # Draw current screen
            if current_screen:
                current_screen.draw(WIN)
            
            pygame.display.flip()


# --- Run Game ---
if __name__ == "__main__":
    Game().run()
